#pragma once

#include "device.hpp"

class DevManager {
public:
    inline static constexpr uint8_t basicModbusAddr = 100;

private:
    using addDeviceAction_t = std::function<void(std::shared_ptr<ModbusDevice_t>)>;
    using delDeviceAction_t = addDeviceAction_t;

    std::vector<std::shared_ptr<ModbusDevice_t>> modbusDeviceList;
    std::vector<addDeviceAction_t> addModbusDeviceAction;
    std::vector<delDeviceAction_t> delModbusDeviceAction;

    std::vector<std::shared_ptr<ModbusDevice_t>> autoFindModbusTempList;

    ModbusRtu *rtu;

    inline static const HardWare_t hardware = {"/dev/ttyS7", 19200, 'E', 8, 1};

    inline static std::vector<HardWare_t> autoFindHardware;

    /* 子点 */
    inline static PointTable_t defaultSubPointTable = {
        {"prodModel", Point_t(0x00, 0x39, 0x11, 0x00, nullptr, std::optional<std::pair<uint32_t, uint32_t>>(std::make_pair(0 * BYTEWIDTH, 3 * BYTEWIDTH)))},
        {"sn", Point_t(0x00, 0x39, 0x11, 0x00, nullptr, std::optional<std::pair<uint32_t, uint32_t>>(std::make_pair(13 * BYTEWIDTH, 32 * BYTEWIDTH)))},
    };

    inline static PointTable_t defaultPointTable = {
        {"ModbusAddr", {0x0100, 0x01, 0x00, 0x10, nullptr, std::nullopt}},
        {"MacAddr", {0x00, 0x05, 0x00, 0x10, nullptr, std::nullopt}},
        {"BtBroadcast", {0x00, 0x01, 0x00, 0x10, nullptr, std::nullopt}},
        {"DeviceInfo", {0x00, 0x39, 0x11, 0x00, std::make_shared<PointTable_t>(defaultSubPointTable), std::nullopt}},
    };

private:
    uint8_t allocModbusAddr(void);
    ModbusDevice_t getDefaultDevice(const HardWare_t &p);

public:
    DevManager();
    ~DevManager();

    std::shared_ptr<ModbusDevice_t> findModbusDeviceById(std::size_t id);
    void addModbusDevice(const ModbusDevice_t &d);
    std::shared_ptr<ModbusDevice_t> findModbusDeviceByMac(const std::string &mac);
    std::shared_ptr<ModbusDevice_t> findModbusDeviceByAddr(uint8_t slaveAddr);
    std::shared_ptr<ModbusDevice_t> findModbusDeviceBySn(const std::string &sn);

    std::vector<std::shared_ptr<ModbusDevice_t>> findModbusDeviceByModel(const std::string &model);

    void registerOnAddModbusDevice(const addDeviceAction_t &fun);
    void registerOnDelModbusDevice(const delDeviceAction_t &fun);

    std::size_t modbusDeviceSize(void) const;

    ModbusRtu *getRtu(void) const;

    void delModbusDevice(std::size_t id);

    void deviceOpenBleBroadcast(const std::vector<HardWare_t> &param);

    void addDeviceToAutoFindTemp(const ModbusDevice_t &d);
    void clearAutoFindTemp(void);
    std::optional<ModbusDevice_t> autoFindDevice(const std::vector<uint8_t> &mac);
};

DevManager::DevManager()
{
    rtu = new ModbusRtu();
}

DevManager::~DevManager()
{
    if (rtu) {
        delete rtu;
    }
}

void DevManager::addModbusDevice(const ModbusDevice_t &d)
{
    if (findModbusDeviceById(d.id.value())) {
        modbus_warn("device id already exist and id --> {}", d.id.value());
        return;
    }

    modbusDeviceList.emplace_back(std::make_shared<ModbusDevice_t>(d));

    findModbusDeviceById(d.id.value())->setModbusRtu(rtu);

    std::for_each(addModbusDeviceAction.begin(), addModbusDeviceAction.end(), [&](const addDeviceAction_t &f) {if (f) {f(findModbusDeviceById(d.id.value()));} });

    modbus_info("device add success id --> {}", d.id.value());
}

void DevManager::delModbusDevice(std::size_t id)
{
    std::for_each(delModbusDeviceAction.begin(), delModbusDeviceAction.end(), [&](const delDeviceAction_t &f) {if (f) {f(findModbusDeviceById(id));} });
    modbusDeviceList.erase(std::remove_if(modbusDeviceList.begin(), modbusDeviceList.end(), [id](const std::shared_ptr<ModbusDevice_t> &p) { return p->id.value() == id; }), modbusDeviceList.end());
}

std::shared_ptr<ModbusDevice_t> DevManager::findModbusDeviceById(std::size_t id)
{
    auto it = std::find_if(modbusDeviceList.begin(), modbusDeviceList.end(), [id](const std::shared_ptr<ModbusDevice_t> &m) { return m->id == id; });
    if (it == modbusDeviceList.end()) {
        return nullptr;
    }

    return *it;
}

std::shared_ptr<ModbusDevice_t> DevManager::findModbusDeviceByMac(const std::string &mac)
{
    auto it = std::find_if(modbusDeviceList.begin(), modbusDeviceList.end(), [mac](const std::shared_ptr<ModbusDevice_t> &m) { return m->mac == mac; });
    if (it == modbusDeviceList.end()) {
        it = std::find_if(autoFindModbusTempList.begin(), autoFindModbusTempList.end(), [mac](const std::shared_ptr<ModbusDevice_t> &m) { return m->mac == mac; });
        if (it == autoFindModbusTempList.end()) {
            return nullptr;
        }
    }

    return *it;
}

std::shared_ptr<ModbusDevice_t> DevManager::findModbusDeviceBySn(const std::string &sn)
{
    auto it = std::find_if(modbusDeviceList.begin(), modbusDeviceList.end(), [sn](const std::shared_ptr<ModbusDevice_t> &m) { return m->uuid.value() == sn; });
    if (it == modbusDeviceList.end()) {
        it = std::find_if(autoFindModbusTempList.begin(), autoFindModbusTempList.end(), [sn](const std::shared_ptr<ModbusDevice_t> &m) { return m->uuid == sn; });
        if (it == autoFindModbusTempList.end()) {
            return nullptr;
        }
    }

    return *it;
}

std::shared_ptr<ModbusDevice_t> DevManager::findModbusDeviceByAddr(uint8_t slaveAddr)
{
    auto it = std::find_if(modbusDeviceList.begin(), modbusDeviceList.end(), [slaveAddr](const std::shared_ptr<ModbusDevice_t> &m) { return m->slaveAddr == slaveAddr; });
    if (it == modbusDeviceList.end()) {
        it = std::find_if(autoFindModbusTempList.begin(), autoFindModbusTempList.end(), [slaveAddr](const std::shared_ptr<ModbusDevice_t> &m) { return m->slaveAddr == slaveAddr; });
        if (it == autoFindModbusTempList.end()) {
            return nullptr;
        }
    }

    return *it;
}

std::vector<std::shared_ptr<ModbusDevice_t>> DevManager::findModbusDeviceByModel(const std::string &model)
{
    std::vector<std::shared_ptr<ModbusDevice_t>> result;
    for (auto &item : modbusDeviceList) {
        if (item->model.has_value() && item->model->find(model) != std::string::npos) {
            result.emplace_back(item);
        }
    }

    return result;
}

void DevManager::registerOnAddModbusDevice(const addDeviceAction_t &fun)
{
    addModbusDeviceAction.emplace_back(fun);
}

void DevManager::registerOnDelModbusDevice(const delDeviceAction_t &fun)
{
    delModbusDeviceAction.emplace_back(fun);
}

std::size_t DevManager::modbusDeviceSize(void) const
{
    return modbusDeviceList.size();
}

ModbusRtu *DevManager::getRtu(void) const
{
    return rtu;
}

uint8_t DevManager::allocModbusAddr(void)
{
    uint8_t addr = basicModbusAddr;

    while (true) {
        if (findModbusDeviceByAddr(addr)) {
            addr++;
        } else {
            break;
        }
    }

    return addr;
}

ModbusDevice_t DevManager::getDefaultDevice(const HardWare_t &p)
{
    ModbusDevice_t dev {};
    dev.setModbusRtu(rtu);
    dev.hardware = p;
    dev.pointTable = defaultPointTable;
    dev.setSlaveAddr(0x00);
    return dev;
}

void DevManager::addDeviceToAutoFindTemp(const ModbusDevice_t &d)
{
    autoFindModbusTempList.emplace_back(std::make_shared<ModbusDevice_t>(d));
}

void DevManager::clearAutoFindTemp(void)
{
    autoFindModbusTempList.clear();
}

std::optional<ModbusDevice_t> DevManager::autoFindDevice(const std::vector<uint8_t> &mac)
{
    for (const auto &p : autoFindHardware) {
        auto dev = getDefaultDevice(p);

        uint16_t addr = allocModbusAddr();

        modbus_info("alloc modbus addr --> {}", addr);
        dev.setSlaveAddr(addr);
        dev.writePoint("ModbusAddr", 0x03, true);

        std::vector<uint8_t> data = {0x00, 0x05};
        data.insert(data.end(), mac.begin(), mac.end());
        data.emplace_back(addr >> 8 & 0xff);
        data.emplace_back(addr & 0xff);

        dev.setSlaveAddr(0x00);
        dev.writePoint("MacAddr", data);

        if (EMBBADDATA == errno) {
            dev.setSlaveAddr(addr);
            if (dev.readPoint("DeviceInfo", true)) {
                modbus_info("read DeviceInfo success");
                return dev;
            }
        }
    }
    return std::nullopt;
}

void DevManager::deviceOpenBleBroadcast(const std::vector<HardWare_t> &param)
{
    autoFindHardware = param;

    constexpr uint16_t btData = 0x0004;

    for (const auto &p : param) {
        auto dev = getDefaultDevice(p);
        dev.writePoint("BtBroadcast", btData, true);
    }
}
